<template>
  <div class="tag-wall">
    <div class="tag-cloud" ref="wrapper" @mouseenter="stopRotate" @mouseleave="startRotate">
      <p v-for="(item, index) in data" :key="index" ref="tag" @click="clickTag(item)"
        @mouseenter="handleTagHover(index)" @mouseleave="handleTagLeave"
        :class="{ 'tag-dimmed': hoveredIndex !== null && hoveredIndex !== index, 'tag-hovered': hoveredIndex === index }">
        {{ item.name }}
      </p>
    </div>
    <div v-if="isLoading" class="tag-loading">
      <div class="tag-loading-spinner"></div>
      <span>加载标签中...</span>
    </div>
    <div v-else-if="data.length === 0" class="tag-empty">
      <i class="fas fa-tags"></i>
      <span>暂无标签</span>
    </div>
  </div>
</template>

<script>
import { getTagsApi } from '@/api/tags'
export default {
  data() {
    return {
      data: [],
      option: {
        radius: 140, // 滚动半径，单位px
        maxFont: 24, // 最大字体大小
        color: null, // 字体颜色。为空时随机
        rotateAngleXbase: 600, // 默认旋转速度基数，数越小速度越快
        rotateAngleYbase: 600,
      },
      tagList: [],
      isRotating: true,
      hoveredIndex: null,
      animationFrameId: null, // 用于 requestAnimationFrame
      isLoading: true,
      // 预设的和谐调色板
      colorPalette: [
        '#6366f1', '#8b5cf6', '#ec4899', '#f59e0b', '#10b981',
        '#3b82f6', '#f472b6', '#f97316', '#22c55e', '#06b6d4',
        '#d946ef', '#ef4444'
      ],
      lastRotateTime: 0,
      rotationSpeed: 1, // 旋转速度系数
    };
  },
  beforeDestroy() {
    this.stopRotate();
  },
  watch: {
    data() {
      this.$nextTick(() => {
        this._initTags();
      });
    },
  },
  mounted() {
    this.fetchTags();
  },
  methods: {
    /**
     * 获取标签数据
     */
    fetchTags() {
      this.isLoading = true;
      getTagsApi().then(res => {
        this.data = res.data;
        this.isLoading = false;
      }).catch(() => {
        this.isLoading = false;
      });
    },
    /**
     * 初始化标签
     */
    _initTags() {
      if (!this.$refs.tag || this.$refs.tag.length === 0) return;

      this.rotateAngleX = Math.PI / this.option.rotateAngleXbase;
      this.rotateAngleY = Math.PI / this.option.rotateAngleYbase;
      this.tagList = [];

      for (var i = 0, length = this.data.length; i < length; i++) {
        // 获取球面上均匀的点的经纬度 θ = arccos( ((2*num)-1)/all - 1); Φ = θ*sqrt(all * π);
        let angleX = Math.acos((2 * (i + 1) - 1) / length - 1);
        let angleY = angleX * Math.sqrt(length * Math.PI);
        // 根据经纬度获取点的坐标，球中心的点坐标是 (0,0,0) x=r*sinθ*cosΦ   y=r*sinθ*sinΦ   z=r*cosθ;
        const x = this.option.radius * Math.sin(angleX) * Math.cos(angleY);
        const y = this.option.radius * Math.sin(angleX) * Math.sin(angleY);
        const z = this.option.radius * Math.cos(angleX);
        if (this.option.color) {
          this.$refs.tag[i].style.color = this.option.color;
        } else {
          // 从调色板中获取颜色
          this.$refs.tag[i].style.color = this.colorPalette[i % this.colorPalette.length];
        }
        // 每个标签对象都有四对值
        var tag = {
          x: x,
          y: y,
          z: z,
          ele: this.$refs.tag[i],
          originalScale: 1,
          targetScale: 1,
          currentOpacity: 0,
          targetOpacity: 1
        };
        this.tagList.push(tag);
        // 设置初始位置
        this.setPosition(tag, this.option.radius, this.option.maxFont);

        // 添加淡入动画
        setTimeout(() => {
          if (tag.ele) tag.ele.style.opacity = "1";
        }, i * 30);
      }
      this.startRotate();
    },
    /**
     * 设置每个标签的坐标位置和字体大小以及透明度
     */
    setPosition(tag, r, maxFont) {
      // 设置每个标签的坐标位置和字体大小以及透明度
      if (this.$refs.wrapper && tag.ele) {
        const translateX = tag.x + this.$refs.wrapper.offsetWidth / 2 - tag.ele.offsetWidth / 2;
        const translateY = tag.y + this.$refs.wrapper.offsetHeight / 2 - tag.ele.offsetHeight / 2;
        const scale = tag.targetScale;
        const opacity = tag.z / r / 2 + 0.7;
        const fontSize = (tag.z / r / 2 + 0.5) * maxFont;

        tag.ele.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`;
        tag.ele.style.opacity = opacity;
        tag.ele.style.fontSize = fontSize + "px";

        // 添加视差效果 - z轴越大的元素阴影越深
        const shadowIntensity = Math.max(0, tag.z / r * 6);
        tag.ele.style.textShadow = `0 ${shadowIntensity}px ${shadowIntensity * 2}px rgba(0,0,0,0.1)`;
      }
    },
    /**
     * 旋转X轴
     */
    rotateX(tag) {
      var cos = Math.cos(this.rotateAngleX);
      var sin = Math.sin(this.rotateAngleX);
      var y1 = tag.y * cos - tag.z * sin;
      var z1 = tag.y * sin + tag.z * cos;
      tag.y = y1;
      tag.z = z1;
    },
    /**
     * 旋转Y轴
     */
    rotateY(tag) {
      var cos = Math.cos(this.rotateAngleY);
      var sin = Math.sin(this.rotateAngleY);
      var x1 = tag.z * sin + tag.x * cos;
      var z1 = tag.z * cos - tag.x * sin;
      tag.x = x1;
      tag.z = z1;
    },
    /**
     * 使用 requestAnimationFrame 实现动画
     */
    animateTags(timestamp) {
      if (!this.isRotating) {
        return;
      }

      // 控制帧率，每16ms执行一次（约60fps）
      const elapsed = timestamp - this.lastRotateTime;
      if (elapsed > 16) {
        this.lastRotateTime = timestamp;

        for (var i = 0; i < this.tagList.length; i++) {
          this.rotateX(this.tagList[i]);
          this.rotateY(this.tagList[i]);

          // 平滑过渡动画
          if (this.tagList[i].currentScale !== this.tagList[i].targetScale) {
            this.tagList[i].currentScale += (this.tagList[i].targetScale - this.tagList[i].currentScale) * 0.1;
          }

          this.setPosition(
            this.tagList[i],
            this.option.radius,
            this.option.maxFont
          );
        }
      }

      this.animationFrameId = requestAnimationFrame(this.animateTags);
    },
    /**
     * 点击标签
     */
    clickTag(item) {
      this.$router.push({
        path: '/tags',
        query: {
          tagId: item.id,
          tagName: item.name
        }
      })
    },
    /**
     * 停止旋转
     */
    stopRotate() {
      this.isRotating = false;
      if (this.animationFrameId) {
        cancelAnimationFrame(this.animationFrameId);
        this.animationFrameId = null;
      }
    },
    /**
     * 开始旋转
     */
    startRotate() {
      if (this.animationFrameId || this.data.length === 0) {
        return;
      }
      this.isRotating = true;
      this.lastRotateTime = performance.now();
      this.animationFrameId = requestAnimationFrame(this.animateTags);
    },
    handleTagHover(index) {
      this.hoveredIndex = index;
      if (index < this.tagList.length) {
        this.tagList[index].targetScale = 1.15;
      }
    },
    handleTagLeave() {
      if (this.hoveredIndex !== null && this.hoveredIndex < this.tagList.length) {
        this.tagList[this.hoveredIndex].targetScale = 1;
      }
      this.hoveredIndex = null;
    },
  },
};
</script>

<style scoped>
.tag-cloud {
  width: 300px;
  height: 300px;
  position: relative;
  color: #333;
  margin: 0 auto;
  text-align: center;
  cursor: default;
  perspective: 800px;
}

.tag-wall {
  position: relative;
  min-height: 300px;
}

.tag-cloud p {
  position: absolute;
  top: 0px;
  left: 0px;
  color: #333;
  font-family: Arial;
  text-decoration: none;
  margin: 0 10px 15px 0;
  line-height: 18px;
  text-align: center;
  font-size: 16px;
  padding: 4px 9px;
  display: inline-block;
  border-radius: 3px;
  transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
  opacity: 0;
  /* 初始透明度为0，用于淡入效果 */
  transform-origin: center;
  will-change: transform, opacity;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
  user-select: none;
}

.tag-cloud p:hover {
  cursor: pointer;
  opacity: 1 !important;
}

.tag-dimmed {
  opacity: 0.1 !important;
  filter: blur(1px);
}

.tag-hovered {
  z-index: 10;
  font-weight: 500 !important;
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.12) !important;
}

.tag-loading,
.tag-empty {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  color: var(--text-secondary);
}

.tag-loading-spinner {
  width: 30px;
  height: 30px;
  border: 3px solid rgba(99, 102, 241, 0.1);
  border-radius: 50%;
  border-top: 3px solid #6366f1;
  animation: spin 1s linear infinite;
  margin-bottom: 10px;
}

.tag-empty {
  color: var(--text-secondary);
  opacity: 0.6;
}

.tag-empty i {
  font-size: 2rem;
  margin-bottom: 10px;
}

@keyframes spin {
  0% {
    transform: rotate(0deg);
  }

  100% {
    transform: rotate(360deg);
  }
}

@media (prefers-color-scheme: dark) {
  .tag-cloud p {
    box-shadow: 0 1px 3px rgba(255, 255, 255, 0.05);
  }

  .tag-hovered {
    box-shadow: 0 4px 8px rgba(255, 255, 255, 0.08) !important;
  }
}
</style>
